

package com.itzhouwei.commerce.redis.aspect;


import com.google.common.collect.Maps;
import com.google.common.util.concurrent.RateLimiter;
import com.itzhouwei.commerce.redis.annotation.MyZwCacheable;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @description: 缓存切面类 让注解生效
 * @author：zw，微信公共号：yingshengzw
 * @date: 2021/8/28
 * @Copyright：公众号：搬砖暗夜码农 | 博客：https://itzhouwei.com - 沉淀、学习、分享、成长，让自己和他人都能有所收获！
 */
@Component
@Aspect
@Slf4j
@RequiredArgsConstructor
@SuppressWarnings("all")
//拿限流操作的前缀
@ConfigurationProperties(prefix = "myzwcacheable.rate.limit")
public class MyZwCacheableAspect {


    private final RedisTemplate redisTemplate;

    /****
     * 切点
     */
//    @Pointcut("@annotation(com.itzhouwei.commerce.redis.annotation.MyZwCacheable)")
//    public void pointCut(){}

    /****
     *
     * <h1>key -需要限流的方法</h1>
     * <h1>value -限流速率</h1>
     */
    private Map<String, Double> map;


    public void setMap(Map<String, Double> map) {
        this.map = map;
    }

    //TODO 初始化 限流操作
    private Map<String, RateLimiter> rateLimiteMap = Maps.newHashMap();


    @PostConstruct
    private void initRateLimiterMap() {
        if (CollectionUtils.isEmpty(map)) {
            map.forEach((k, v) -> {
                //初始化限流器操作
                RateLimiter rateLimiter = RateLimiter.create(v);
                rateLimiteMap.put(k, rateLimiter);
            });
        }
    }


    /****
     * <h1>增强操作 </h1>
     * @param joinPoint 链接点
     * @param cacheable 切点
     * @return  1参数传递 +@annotation(cacheable)   指定的包下面的类
     * 方法的引用
     * MyZwCacheable ：：new
     */
    @Around(value = "@annotation(cacheable)")
    public Object doAroundCache(ProceedingJoinPoint joinPoint, MyZwCacheable cacheable) throws Throwable {
        //获取缓存的key操作
        String cacheKey = getCacheKey(joinPoint, cacheable);

        //判断缓存当中是否存在 如果存在则直接返回
        Object value = redisTemplate.opsForValue().get(cacheKey);
        if (value != null) {
            log.info("key:{},value:{}", cacheKey, value);
            return value;
        }

        //TODO 2.0版本 新增限流的处理操作 RateLimiter 底层是令牌桶算法
        //1.获取限流器操作
         this.rateLimiter(joinPoint, cacheable);


        //否则查询数据库  执行原有的方法
        value = joinPoint.proceed();
        //过期时间的设置
        if (cacheable.expireInSeconds() <= 0) {
            redisTemplate.opsForValue().set(cacheKey, value);
        } else {
            redisTemplate.opsForValue().set(cacheKey, value, cacheable.expireInSeconds(), TimeUnit.SECONDS);
        }
        return value;

    }

    /****
     * 增加限流操作的方法
     * @param joinPoint
     * @param cacheable
     */
    private void rateLimiter(ProceedingJoinPoint joinPoint, MyZwCacheable cacheable) {
        MethodSignature signature= (MethodSignature) joinPoint.getSignature();
        RateLimiter rateLimiter=rateLimiteMap.get(signature.getMethod().getName());
        if (rateLimiter!=null){
            int timeout= cacheable.waitInSeconds();
            if (timeout<=0) {
                rateLimiter.acquire();
            }else{
                boolean aquire=rateLimiter.tryAcquire(timeout,TimeUnit.SECONDS);
                //获取不成功
                if (!aquire){
                    //抛出自定义异常
                    throw  new RuntimeException("系统繁忙");
                }
            }
        }
    }

    /****
     * 获取缓存redis key 操作
     * @param joinPoint
     * @param cacheable
     * @return
     */
    private String getCacheKey(ProceedingJoinPoint joinPoint, MyZwCacheable cacheable) {
        //获取缓存的key前缀
        String cacheName = cacheable.cacheName();
        //生成key的操作
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(cacheable.key());

        EvaluationContext context = new StandardEvaluationContext();

        //获取方法签名 动态的设置上下文
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(signature.getMethod());

        Object[] args = joinPoint.getArgs();


        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
        }

        //key=zw:user:1
        String cacheKey = cacheable.cacheName() + expression.getValue(context).toString();
        return cacheKey;
    }

}
